package top.mable.interceptors;

/**
 * @author taffywen
 * @date 2022/3/11 14:55
 */

import com.alibaba.druid.sql.ast.SQLStatement;
import com.alibaba.druid.sql.ast.statement.SQLBlockStatement;
import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlDeleteStatement;
import com.alibaba.druid.sql.dialect.mysql.parser.MySqlStatementParser;
import com.baomidou.mybatisplus.core.metadata.TableFieldInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.factory.DefaultObjectFactory;
import org.apache.ibatis.reflection.wrapper.DefaultObjectWrapperFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.sql.SQLException;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;

/**
 * @author wen
 * @desc 删除表 把删除的数据插入到指定表  拦截器
 */
@Slf4j
@Intercepts(@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}))
public class DeleteInterceptor implements Interceptor {

    /**
     * 排除表，以英文逗号隔开
     */
    @Value("${exclude.table}")
    private String excludeTable;

    private static final String SQL_BLOCK_BEGIN = "BEGIN";
    private static final String SQL_BLOCK_END = "END;";
    private Map<String, String> map = new ConcurrentHashMap<>();
    private Map<String, String> cache = new ConcurrentHashMap<>();

    @Override
    public Object intercept(Invocation invocation) throws Throwable {

        loadExcludeTable();

        MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
        Object parameterObj = invocation.getArgs()[1];
        //获取sql语句
        String sql = mappedStatement.getBoundSql(parameterObj).getSql();

        /**
         * 如果是删除操作时，先写入数据到删除表
         * 删除表的规则是第一个字段为主键，后面字段顺序与原表保持一致
         * 删除表的后缀以下划线+del结束
         */
        if (SqlCommandType.DELETE.equals(mappedStatement.getSqlCommandType())) {

            MySqlDeleteStatement deleteStatement = null;
            //解析语句
            MySqlStatementParser parser = new MySqlStatementParser(sql);

            //判断是否是代码块
            if (StringUtils.startsWithIgnoreCase(sql, SQL_BLOCK_BEGIN)) {
                //解析代码块
                SQLBlockStatement sqlStatement = (SQLBlockStatement) parser.parseBlock();
                //获取代码块语句
                List<SQLStatement> statementList = sqlStatement.getStatementList();
                if (CollectionUtils.isEmpty(statementList)) {
                    return invocation.proceed();
                }

                //新的block块代码
                StringBuffer blockSb = new StringBuffer(SQL_BLOCK_BEGIN).append(" ");
                AtomicBoolean isExecute = new AtomicBoolean(false);

                statementList.parallelStream().forEach(s -> {
                    MySqlDeleteStatement deleteSqlStatement = (MySqlDeleteStatement) s;
                    String tableName = deleteSqlStatement.getTableName().toString();
                    if (!map.containsKey(tableName.toUpperCase())) {
                        isExecute.set(true);
                        //得到insert语句
                        log.info("delete 组装sql：{}", deleteSqlStatement.toString());
                        String insertSql = createInsertSql(tableName, deleteSqlStatement.toString());
                        blockSb.append(insertSql);
                    }
                });
                blockSb.append(SQL_BLOCK_END);

                if (!isExecute.get()) {
                    return invocation.proceed();
                }

                //重置sql为insert语句
                resetSql(invocation, blockSb.toString(), SqlCommandType.INSERT);
                //执行insert语句
                //执行insert语句
                try {
                    invocation.proceed();
                } catch (Exception e) {
                    log.info("备份删除执行异常!", e);
                }
                //再重置回原来的语句
                resetSql(invocation, sql, SqlCommandType.DELETE);

            } else if (StringUtils.startsWithIgnoreCase(sql, SqlCommandType.DELETE.name())) {
                deleteStatement = parser.parseDeleteStatement();
                //得到sql语句的表名
                String tableName = deleteStatement.getTableName().toString();

                if (map.containsKey(tableName.toUpperCase())) {
                    return invocation.proceed();
                }

                String insertSql = createInsertSql(tableName, sql);
                resetSql(invocation, insertSql, SqlCommandType.INSERT);
                try {
                    invocation.proceed();
                } catch (Exception e) {
                    log.info("备份删除执行异常!，delete:{},insert:{}", sql, insertSql, e);
                }
                //再重置回原来的语句
                resetSql(invocation, sql, SqlCommandType.DELETE);
            }
        }

        return invocation.proceed();
    }

    /**
     * @param invocation
     * @param sql
     * @throws SQLException
     * @desc 把新sql重置到invocation中
     */
    private void resetSql(Invocation invocation, String sql, SqlCommandType sqlCommandType) throws SQLException {
        final Object[] args = invocation.getArgs();
        MappedStatement statement = (MappedStatement) args[0];
        BoundSql boundSql = statement.getBoundSql(args[1]);
        MappedStatement newStatement = createNewMappedStatement(statement, new BoundSqlSqlSource(boundSql), sqlCommandType);
        MetaObject msObject = MetaObject.forObject(newStatement, new DefaultObjectFactory(), new DefaultObjectWrapperFactory(), new DefaultReflectorFactory());
        msObject.setValue("sqlSource.boundSql.sql", sql);
        args[0] = newStatement;
    }

    /**
     * @param ms
     * @param newSqlSource
     * @return
     * @desc 创建新的sql映射对象
     */
    private MappedStatement createNewMappedStatement(MappedStatement ms, SqlSource newSqlSource, SqlCommandType sqlCommandType) {
        MappedStatement.Builder builder =
                new MappedStatement.Builder(ms.getConfiguration(), ms.getId(), newSqlSource, sqlCommandType);
        builder.statementType(ms.getStatementType());
        builder.timeout(ms.getTimeout());
        builder.parameterMap(ms.getParameterMap());
        builder.resultMaps(ms.getResultMaps());
        builder.resultSetType(ms.getResultSetType());
        builder.cache(ms.getCache());
        builder.flushCacheRequired(ms.isFlushCacheRequired());
        builder.useCache(ms.isUseCache());

        return builder.build();
    }

    class BoundSqlSqlSource implements SqlSource {
        private BoundSql boundSql;

        public BoundSqlSqlSource(BoundSql boundSql) {
            this.boundSql = boundSql;
        }

        @Override
        public BoundSql getBoundSql(Object parameterObject) {
            return boundSql;
        }
    }

    /**
     * @param tableName 表名
     * @param sql       sql语句
     * @return
     * @desc 创建insert语句
     */
    private String createInsertSql(String tableName, String sql) {
        log.info("table ----{},sql-----{}", tableName, sql);
        if (cache.containsKey(sql)) {
            return cache.get(sql);
        }
        String tableAndCondition = sql.substring(sql.indexOf(tableName));
        //String id = String.valueOf(SnowFlakeUtil.generateId());
        //String id = UUID.randomUUID().toString().replaceAll("-", "");
        String fields = getFieldList(tableName);
        StringBuilder sbSql = new StringBuilder("INSERT INTO ");
        sbSql.append(tableName)
                .append("_DEL ")
                .append(StringPool.LEFT_BRACKET)
                .append("PKID,")
                .append(fields)
                .append(StringPool.RIGHT_BRACKET)
                .append(" SELECT sys_guid() ").append(",").append(fields).append(" FROM ")
                .append(tableAndCondition);
        String insertSql = sbSql.toString();
        cache.put(sql, insertSql);
        return insertSql;
    }

    private String getFieldList(String tableName) {
        TableInfo tableInfo = TableInfoHelper.getTableInfo(tableName.toUpperCase());
        String keyColuman = tableInfo.getKeyColumn();
        List<TableFieldInfo> tableFieldInfoList = tableInfo.getFieldList();

        List<String> fields = tableFieldInfoList.stream().map(tableFieldInfo -> tableFieldInfo.getColumn()).collect(Collectors.toList());
        if (!StringUtils.isEmpty(keyColuman)) {
            fields.add(keyColuman);
        }
        return String.join(",", fields);
    }

    private void loadExcludeTable() {
        log.debug("数据库删除记录排除表集合: {}", excludeTable);
        if (StringUtils.isEmpty(excludeTable)) {
            return;
        }
        //获取排除表，如果是排除表则不处理
        for (String table : excludeTable.split(",")) {
            if (!map.containsKey(table.toUpperCase())) {
                map.put(table.toUpperCase(), "");
            }
        }
    }

}